package com.supreme.threadserver.service;

import com.supreme.threadserver.config.ThreadPoolTaskConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.function.*;

@Slf4j
@Service
public class JucUtilService {

    @Resource
    private ThreadPoolTaskExecutor supremeTaskExecutor;

    private Semaphore semaphore;

    private CyclicBarrier cyclicBarrier;

    @PostConstruct
    public void initJucUtilService() {
        semaphore = new Semaphore(2);
        cyclicBarrier = new CyclicBarrier(4, () -> {
            log.info("CyclicBarrier GO!!!");
        });
    }

    @Async(value = ThreadPoolTaskConfig.SUPREME_TASK_EXECUTOR)
    public void countDownLatchDemo(CountDownLatch countDownLatch, long waitDuration) {
        String threadName = Thread.currentThread().getName();

        try {
            System.out.println("threadName = " + threadName + " Sleep Start = " + waitDuration);
            TimeUnit.SECONDS.sleep(waitDuration);
            System.out.println("threadName = " + threadName + " Sleep End");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Optional.ofNullable(countDownLatch).ifPresent(CountDownLatch::countDown);
        System.out.println("threadName = " + threadName + " Finished");
    }

    @Async(value = ThreadPoolTaskConfig.SUPREME_TASK_EXECUTOR)
    public void cyclicBarrierDemo(CyclicBarrier cyclicBarrier, long waitDuration) {
        final String threadName = Thread.currentThread().getName();

        try {
            System.out.printf("Thread = %s Enter Sleep = %d\n", threadName, waitDuration);
            TimeUnit.SECONDS.sleep(waitDuration);
            System.out.printf("Thread = %s Enter Await\n", threadName);
            cyclicBarrier.await();
            System.out.printf("Thread = %s Enter Finished\n", threadName);
        } catch (InterruptedException | BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
    }

    @Async(value = ThreadPoolTaskConfig.SUPREME_TASK_EXECUTOR)
    public void semaphoreDemo(Semaphore semaphore, long waitDuration) {
        final String threadName = Thread.currentThread().getName();

        try {
            System.out.println("threadName = " + threadName + " Acquire = " + waitDuration);
            semaphore.acquire();
            TimeUnit.SECONDS.sleep(waitDuration);
            System.out.println("threadName = " + threadName + " Process");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            semaphore.release();
        }
    }

    public void completableFutureDemo() {
        System.out.println("mainThreadName: " + Thread.currentThread().getName());

        int count = 5;
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                System.out.println("[supplyAsync] ThreadName: " +  Thread.currentThread().getName());
                int sumAmount = 0;

                for (int i = 0; i < count; i++) {
                    try {
                        System.out.println("CompletableFuture get: " + i);
                        sumAmount += i;
                        TimeUnit.MILLISECONDS.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                return sumAmount;
            }
        }).thenApply(new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                System.out.println("[thenApply] ThreadName: " + Thread.currentThread().getName());
                System.out.println("CompletableFuture thenApply: " + integer);
                return integer * 10;
            }
        }).whenComplete(new BiConsumer<Integer, Throwable>() {
            @Override
            public void accept(Integer integer, Throwable throwable) {
                System.out.println("[whenComplete] ThreadName: " + Thread.currentThread().getName());
                System.out.println("CompletableFuture whenComplete: " + integer);
            }
        }).handle(new BiFunction<Integer, Throwable, Integer>() {
            @Override
            public Integer apply(Integer integer, Throwable throwable) {
                System.out.println("[handle] ThreadName: " + Thread.currentThread().getName());
                System.out.println("CompletableFuture handle: " + integer);
                return integer * 10;
            }
        });

        future.thenAccept(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("[thenAccept] ThreadName: " + Thread.currentThread().getName());
                System.out.println("CompletableFuture thenAccept: " + integer);
            }
        });

        try {
            final Integer futureResult = future.get();
            System.out.println("futureResult: " + futureResult);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public void testCompletableFuture(int threadCount) {
        long randomValue = RandomUtils.nextLong(1, threadCount);
        CompletableFuture<?>[] completableFutureArray = new CompletableFuture[threadCount];

        for (int i = 0; i < threadCount; i++) {
            int index = i;
            completableFutureArray[i] = CompletableFuture.runAsync(() -> {
                String name = Thread.currentThread().getName();
                log.info("Thread = {} Started", name);
                try {
                    if (index + 1 == randomValue) {
                        TimeUnit.SECONDS.sleep(30);
                    } else {
                        TimeUnit.SECONDS.sleep(10);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("Thread = {} End", name);
            }, supremeTaskExecutor);
        }

        CompletableFuture.allOf(completableFutureArray).whenComplete((unused, throwable) -> {
            String name = Thread.currentThread().getName();
            log.info("Thread = {} whenComplete", name);
        });
    }

    public void testCountDownLatch(int threadCount) {
        long randomValue = RandomUtils.nextLong(1, threadCount);
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        String mainName = Thread.currentThread().getName();

        for (int i = 0; i < threadCount; i++) {
            int index = i;
            CompletableFuture.runAsync(() -> {
                String name = Thread.currentThread().getName();
                log.info("Thread = {} Started", name);
                try {
                    if (index + 1 == randomValue) {
                        TimeUnit.MINUTES.sleep(2);
                    } else {
                        TimeUnit.MINUTES.sleep(1);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("Thread = {} End1", name);
                countDownLatch.countDown();
                log.info("Thread = {} End2", name);
            }, supremeTaskExecutor);
        }

        try {
            log.info("Thread = {} countDownLatch Await Started", mainName);
            countDownLatch.await();
            log.info("Thread = {} countDownLatch Await End", mainName);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public void testCyclicBarrier(long timeout) {
        CompletableFuture.runAsync(() -> {
            String name = Thread.currentThread().getName();

            try {
                int numberWaiting = cyclicBarrier.getNumberWaiting();
                log.info("Thread = {} Ready numberWaiting = {}", name, numberWaiting);
                int await = cyclicBarrier.await();
//                int await = cyclicBarrier.await(timeout, TimeUnit.SECONDS);

                log.info("Thread = {} Ready Done {}", name, await);
            } catch (InterruptedException | BrokenBarrierException e) {
//                log.error("Error = {}", e.getMessage());
//                cyclicBarrier.reset();
            } finally {
                log.info("Thread = {} Finished", name);
            }
        }, supremeTaskExecutor);
    }

    public void testSemaphore(int permits, long timeout) {
        CompletableFuture.runAsync(() -> {
            String name = Thread.currentThread().getName();

            int availablePermits = semaphore.availablePermits();
            int queueLength = semaphore.getQueueLength();
            log.info("Thread = {} availablePermits = {} queueLength = {}", name, availablePermits, queueLength);

            try {
                log.info("Thread = {} Will Enter", name);
                semaphore.acquire(permits);

                log.info("Thread = {} Processing", name);
                TimeUnit.SECONDS.sleep(timeout);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                log.info("Thread = {} Finished", name);
                semaphore.release(permits);
            }
        }, supremeTaskExecutor);
    }

    public void testSemaphoreTry(int permits, long timeout) {
        CompletableFuture.runAsync(() -> {
            String name = Thread.currentThread().getName();

            try {
                log.info("Thread = {} Will Enter", name);
                boolean tryAcquire = semaphore.tryAcquire(permits);

                log.info("Thread = {} Processing tryAcquire = {}", name, tryAcquire);
                TimeUnit.SECONDS.sleep(timeout);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                log.info("Thread = {} Finished", name);
                semaphore.release(permits);
            }
        }, supremeTaskExecutor);
    }
}
